#include <errno.h>
#include <stdlib.h>
#include "rmath.h"
#include "redintobject.h"
#include "redmapobject.h"
#include "redboolobject.h"
#include "redfloatobject.h"
#include "redstringobject.h"
#include "redexceptionobject.h"

static RedObject *int_repr(RedObject *self)
{
	return RedString_FromCFormat("%lld", RedInt_Value(self));
}

static uint32_t int_hash(RedObject *self)
{
	return rmath_hash_i64(RedInt_Value(self));
}

#define apply_unary(op)				{ return RedInt_FromInt(op RedInt_Value(self)); }

#define apply_binary(op)																\
	{																					\
		if (RedInt_Exact(other))														\
			return RedInt_FromInt(RedInt_Value(self) op RedInt_Value(other));			\
																						\
		if (RedFloat_Exact(other))														\
			return RedFloat_FromFloat(RedInt_Value(self) op RedFloat_Value(other));		\
																						\
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");		\
		return NULL;																	\
	}

#define apply_binary_i64(op)															\
	{																					\
		if (RedInt_Exact(other))														\
			return RedInt_FromInt(RedInt_Value(self) op RedInt_Value(other));			\
																						\
		RedException_ThrowFormat(&RedExc_TypeError, "'int' expected");					\
		return NULL;																	\
	}

#define apply_binary_bool(op)															\
	{																					\
		if (RedInt_Exact(other))														\
		{																				\
			if (RedInt_Value(self) op RedInt_Value(other))								\
				Red_Return_True();														\
			else																		\
				Red_Return_False();														\
		}																				\
																						\
		if (RedFloat_Exact(other))														\
		{																				\
			if (RedInt_Value(self) op RedFloat_Value(other))							\
				Red_Return_True();														\
			else																		\
				Red_Return_False();														\
		}																				\
																						\
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");		\
		return NULL;																	\
	}

#define apply_binary_relation(op)														\
	{																					\
		if ((RedInt_Value(self) ? 1 : 0) op RedBool_IsTrue(other))						\
			Red_Return_True();															\
		else																			\
			Red_Return_False();															\
	}

static RedObject *int_pos(RedObject *self)							apply_unary(+)
static RedObject *int_neg(RedObject *self)							apply_unary(-)
static RedObject *int_bit_not(RedObject *self)						apply_unary(~)

static RedObject *int_add(RedObject *self, RedObject *other)		apply_binary(+)
static RedObject *int_sub(RedObject *self, RedObject *other)		apply_binary(-)
static RedObject *int_mul(RedObject *self, RedObject *other)		apply_binary(*)
static RedObject *int_bit_or(RedObject *self, RedObject *other)		apply_binary_i64(|)
static RedObject *int_bit_and(RedObject *self, RedObject *other)	apply_binary_i64(&)
static RedObject *int_bit_xor(RedObject *self, RedObject *other)	apply_binary_i64(^)

static RedObject *int_lshift(RedObject *self, RedObject *other)		apply_binary_i64(<<)
static RedObject *int_rshift(RedObject *self, RedObject *other)		apply_binary_i64(>>)
static RedObject *int_bool_or(RedObject *self, RedObject *other)	apply_binary_relation(||)
static RedObject *int_bool_and(RedObject *self, RedObject *other)	apply_binary_relation(&&)
static RedObject *int_bool_xor(RedObject *self, RedObject *other)	apply_binary_relation(!=)

static RedObject *int_le(RedObject *self, RedObject *other)			apply_binary_bool(<)
static RedObject *int_ge(RedObject *self, RedObject *other)			apply_binary_bool(>)
static RedObject *int_leq(RedObject *self, RedObject *other)		apply_binary_bool(<=)
static RedObject *int_geq(RedObject *self, RedObject *other)		apply_binary_bool(>=)

#undef apply_unary
#undef apply_binary
#undef apply_binary_i64
#undef apply_binary_bool
#undef apply_binary_relation

static RedObject *int_div(RedObject *self, RedObject *other)
{
	int64_t a = RedInt_Value(self);

	if (RedInt_Exact(other))
	{
		int64_t b = RedInt_Value(other);

		if (b != 0)
			return RedInt_FromInt(a / b);

		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}
	else if (RedFloat_Exact(other))
	{
		float b = RedFloat_Value(other);

		if (b != 0.0)
			return RedFloat_FromFloat(a / b);

		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");
		return NULL;
	}
}

static RedObject *int_mod(RedObject *self, RedObject *other)
{
	if (!RedInt_Exact(other))
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'int' expected");
		return NULL;
	}

	int64_t a = RedInt_Value(self);
	int64_t b = RedInt_Value(other);

	if (b != 0)
		return RedInt_FromInt(a % b);

	RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
	return NULL;
}

static RedObject *int_pow(RedObject *self, RedObject *other)
{
	int64_t a = RedInt_Value(self);

	if (a < 0)
	{
		RedException_ThrowFormat(&RedExc_FloatPointError, "Divide by zero");
		return NULL;
	}

	if (a == 0)
		return RedInt_FromInt(0);

	if (RedInt_Exact(other))
	{
		int64_t b = RedInt_Value(other);

		if (b >= 0)
			return RedInt_FromInt(rmath_pow_i64(a, b));
		else
			return RedFloat_FromFloat(rmath_pow_d64_2(a, b));
	}
	else if (RedFloat_Exact(other))
	{
		float b = RedFloat_Value(other);

		if (b == 0.0f)
			return RedInt_FromInt(1);
		else
			return RedFloat_FromFloat(rmath_pow_d64_2(a, b));
	}
	else
	{
		RedException_ThrowFormat(&RedExc_TypeError, "'int' or 'float' expected");
		return NULL;
	}
}

static RedObject *int_bool_not(RedObject *self)
{
	if (!RedInt_Value(self))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *int_eq(RedObject *self, RedObject *other)
{
	if ((RedInt_Exact(other) && (RedInt_Value(self) == RedInt_Value(other))) ||
		(RedFloat_Exact(other) && (RedInt_Value(self) == RedFloat_Value(other))))
		Red_Return_True();
	else
		Red_Return_False();
}

static RedObject *int_neq(RedObject *self, RedObject *other)
{
	if ((!RedInt_Exact(other) || (RedInt_Value(self) != RedInt_Value(other))) &&
		(!RedFloat_Exact(other) || (RedInt_Value(self) != RedFloat_Value(other))))
		Red_Return_True();
	else
		Red_Return_False();
}

RedTypeObject RedType_Int =
{
	RedType_INIT("int", RedIntObject, RedType_Object)

	.tp_str			= int_repr,
	.tp_repr		= int_repr,
	.tp_hash		= int_hash,

	.tp_add			= int_add,
	.tp_sub			= int_sub,
	.tp_mul			= int_mul,
	.tp_div			= int_div,
	.tp_mod			= int_mod,
	.tp_pow			= int_pow,
	.tp_bit_or		= int_bit_or,
	.tp_bit_and		= int_bit_and,
	.tp_bit_xor		= int_bit_xor,
	.tp_bit_not		= int_bit_not,
	.tp_lshift		= int_lshift,
	.tp_rshift		= int_rshift,

	.tp_aug_add		= int_add,
	.tp_aug_sub		= int_sub,
	.tp_aug_mul		= int_mul,
	.tp_aug_div		= int_div,
	.tp_aug_mod		= int_mod,
	.tp_aug_pow		= int_pow,
	.tp_aug_or		= int_bit_or,
	.tp_aug_and		= int_bit_and,
	.tp_aug_xor		= int_bit_xor,
	.tp_aug_lshift	= int_lshift,
	.tp_aug_rshift	= int_rshift,

	.tp_bool_or		= int_bool_or,
	.tp_bool_and	= int_bool_and,
	.tp_bool_xor	= int_bool_xor,
	.tp_bool_not	= int_bool_not,

	.tp_eq			= int_eq,
	.tp_le			= int_le,
	.tp_ge			= int_ge,
	.tp_neq			= int_neq,
	.tp_leq			= int_leq,
	.tp_geq			= int_geq,

	.tp_pos			= int_pos,
	.tp_neg			= int_neg,
};

static RedObject *int_pool = NULL;

void Red_FreeIntegerPool(void)
{
	Red_UNREF(int_pool);
}

char Red_InitIntegerPool(void)
{
	int_pool = RedPool_Create();
	return int_pool != NULL;
}

RedObject *RedInt_FromInt(int64_t value)
{
	RedObject *result = NULL;

	/* lookup from pool */
	if (value >= -128 && value < 127)
		if ((result = RedPool_GetInt(int_pool, value)))
			return result;

	/* not found, create one */
	result = RedObject_Create(&RedType_Int, NULL);

	if (!result)
		return NULL;

	/* cache it up */
	if (value >= -128 && value < 127)
		RedPool_PutInt(int_pool, value, result);

	RedInt_Value(result) = value;
	return result;
}

RedObject *RedInt_FromFloat(float value)
{
	return RedInt_FromInt((int64_t)value);
}

RedObject *RedInt_FromBool(RedObject *value)
{
	return RedInt_FromInt(RedBool_IsTrue(value) ? 1 : 0);
}

RedObject *RedInt_FromString(RedObject *value, int base)
{
	return RedInt_FromCString(RedString_Buffer(value), base);
}

RedObject *RedInt_FromCString(const char *value, int base)
{
	char *tailptr;
	int64_t result = strtol(value, &tailptr, base);

	if (result == 0 && (errno != 0 || *tailptr != '\0'))
	{
		RedException_ThrowErrno(&RedExc_ValueError);
		return NULL;
	}

	return RedInt_FromInt(result);
}
